/*
 *  engine.cpp
 */

#include <list>
#include <string>
#include <vector>
#include "../../common/src/debug.h"
#include "../../common/src/lock.h"
#include "../../common/src/message_queue.h"
#include "../../common/src/session.h"
#include "../../common/src/socket_exception.h"
#include "../../common/src/util.h"
#include "../../model/src/campaign.h"
#include "../../model/src/game.h"
#include "../../model/src/player.h"
#include "engine.h"
#include "session_dispatcher.h"
#include "server_app.h"

int Engine::player_gen_id = 0;
int Engine::game_gen_id = 0;

int Engine::nextPlayerID() {
    return ++(Engine::player_gen_id);
}

int Engine::nextGameID() {
    return ++(Engine::game_gen_id);
}

Engine::Engine(ServerApp* app) : games(), players() {
    this->app = app;
}

Engine::~Engine() {
    Lock games_lock(games.getMutex());
    std::list<Game*>* l = games.getList();
    for (std::list<Game*>::iterator it = l->begin(); it != l->end(); it++) {
        delete *it;
    }
    Lock players_lock(players.getMutex());
    std::list<Player*>* pl = players.getList();
    for (std::list<Player*>::iterator it = pl->begin(); it != pl->end(); it++) {
        delete *it;
    }
}

void Engine::run() {
    while (app->getRunning()) {
        processMessages();
        //loop model logic
    }
}

void Engine::processMessages() {
    while (!app->getMessageQueue().empty()) {
        Message* msg = app->getMessageQueue().pop();
        Session* session = getSession(msg->getFrom());
        if (session != NULL) {
            if (msg->getType() == Protocol::MSG_LIST_GAMES) {
                sendGameList(session);
            } else if (msg->getType() == Protocol::MSG_CREATE_GAME) {
                createGame(session, msg);
            } else if (msg->getType() == Protocol::MSG_GAME_ABORT) {
                abortGame(session, msg);
            } else if (msg->getType() == Protocol::MSG_JOIN_GAME) {
                joinGame(session, msg);
            } else if (msg->getType() == Protocol::MSG_START_GAME) {
                startGame(session);
            } else if (msg->getType() == Protocol::MSG_DISCONNECTED) {
                disconnect(session);
            }
        }
        delete msg;
    }
}

void Engine::createGame(Session* session, Message* msg) {    
    //Avoid multiple game creations from the same client
    Lock players_lock(players.getMutex());
    if (getPlayer(session->getID()) != NULL) {
        std::string txt("Player already created/joined a game");
        session->sendMessage(new Message(session->getID(),
                                         Protocol::MSG_CREATE_GAME_FAIL,
                                         txt));
        return;
    }
    std::vector<std::string>* records = msg->getRecords();
    if (records == NULL || records->size() != 1) {
        delete records;
        std::string txt("Invalid params");
        session->sendMessage(new Message(session->getID(),
                                         Protocol::MSG_CREATE_GAME_FAIL,
                                         txt));
        return;
    }
    std::vector<std::string>* units = msg->getUnits((*records)[0]);
    delete records;
    if (units == NULL || units->size() != 3) {
        delete units;
        std::string txt("Invalid params");
        session->sendMessage(new Message(session->getID(),
                                         Protocol::MSG_CREATE_GAME_FAIL,
                                         txt));
        return;
    }
    std::string playerName((*units)[0]);
    std::string gameName((*units)[1]);
    std::string campaignID((*units)[2]);
    delete units;
    Player* player = new Player(nextPlayerID(), session, playerName);
    players.getList()->push_back(player);
    Campaign* campaign = getCampaign(Util::stoi(campaignID));
    Game* game = new Game(nextGameID(), *campaign, gameName, player);
    Lock lock(games.getMutex());
    games.getList()->push_back(game);
    session->sendMessage(new Message(session->getID(),
                                     Protocol::MSG_CREATE_GAME_OK,
                                     Util::itos(game->getID())));
}

void Engine::abortGame(Session* session, Message* msg) {
    const std::string sessionID = session->getID();
    Lock players_lock(players.getMutex());
    Lock games_lock(games.getMutex());

    Player* player = getPlayer(sessionID);
    if (player == NULL) {
        std::string txt("Player not found");
        session->sendMessage(new Message(sessionID,
                                         Protocol::MSG_GAME_ABORT_FAIL,
                                         txt));
        return;
    }

    std::list<Game*>* l = games.getList();
    Game* game = getGame(player, false);
    if (game == NULL) {
        std::string txt("Game is not available");
        session->sendMessage(new Message(sessionID,
                                         Protocol::MSG_GAME_ABORT_FAIL,
                                         txt));
        return;
    }
    
    std::list<Player*>* all_players = players.getList();
    std::vector<Player*>* game_players = game->getPlayers();
    if (game->isActive() || player == game->getOwner()) {
        for (int i = 0; i < game_players->size(); ++i) {
            (*game_players)[i]->getSession()->sendMessage(
                           new Message(sessionID, Protocol::MSG_GAME_ABORT_OK));
            all_players->remove((*game_players)[i]);
            delete (*game_players)[i];
        }
        game_players->clear();
        l->remove(game);
        delete game;
    } else {
        player->getSession()->sendMessage(
                new Message(sessionID, Protocol::MSG_GAME_ABORT_OK));
        game->getOwner()->getSession()->sendMessage(
                new Message(sessionID, Protocol::MSG_PLAYER_DISSEVERED));
        all_players->remove(player);
        for (int i = 0; i < game_players->size(); ++i) {
            if ((*game_players)[i]->getID() == player->getID()) {
                game_players->erase(game_players->begin() + i);
                break;
            } 
        }
        delete player;
    }
}

void Engine::joinGame(Session* session, Message* msg) {
    Lock players_lock(players.getMutex());
    //Avoid multiple game joins from the same client
    if (getPlayer(session->getID()) != NULL) {
        std::string txt("Player already created/joined a game");
        session->sendMessage(new Message(session->getID(),
                                         Protocol::MSG_JOIN_GAME_FAIL,
                                         txt));
        return;
    }
    std::vector<std::string>* records = msg->getRecords();
    if (records == NULL || records->size() != 1) {
        delete records;
        std::string txt("Invalid params");
        session->sendMessage(new Message(session->getID(),
                                         Protocol::MSG_JOIN_GAME_FAIL,
                                         txt));
        return;
    }
    std::vector<std::string>* units = msg->getUnits((*records)[0]);
    delete records;
    if (units == NULL || units->size() != 2) {
        delete units;
        std::string txt("Invalid params");
        session->sendMessage(new Message(session->getID(),
                                         Protocol::MSG_JOIN_GAME_FAIL,
                                         txt));
        return;
    }
    std::string playerName((*units)[0]);
    std::string gameID((*units)[1]);
    delete units;    
    Lock lock(games.getMutex());
    std::list<Game*>* l = games.getList();
    Game* game = getGame(Util::stoi(gameID));
    //Cannot join active games
    if (game == NULL || game->isActive()) {
        std::string txt("Game is not available");
        session->sendMessage(new Message(session->getID(),
                                         Protocol::MSG_JOIN_GAME_FAIL,
                                         txt));
        return;
    }
    Player* player = new Player(nextPlayerID(), session, playerName);
    players.getList()->push_back(player);
    game->addPlayer(player);
    session->sendMessage(new Message(session->getID(),
                                     Protocol::MSG_JOIN_GAME_OK,
                                     Util::itos(game->getID())));
    game->getOwner()->getSession()->sendMessage(new Message(session->getID(),
                                                  Protocol::MSG_PLAYER_JOINED));
}

void Engine::startGame(Session* session) {
    Lock players_lock(players.getMutex());
    //Avoid multiple game joins from the same client
    Player* player = getPlayer(session->getID());
    if (player == NULL) {
        std::string txt("Game start denied");
        session->sendMessage(new Message(session->getID(),
                                         Protocol::MSG_START_GAME_FAIL,
                                         txt));
        return;
    }
    Lock lock(games.getMutex());
    Game* game = getGame(player, true);
    if (game == NULL) {
        std::string txt("Game is not available");
        session->sendMessage(new Message(session->getID(),
                                         Protocol::MSG_START_GAME_FAIL,
                                         txt));
        return;
    } else if (game->isActive()) {
        std::string txt("Game already started");
        session->sendMessage(new Message(session->getID(),
                                         Protocol::MSG_START_GAME_FAIL,
                                         txt));
        return;
    }
    game->activate();
    //synchro game players?
    std::vector<Player*>* v = game->getPlayers();
    Message* msg;
    for (int i = 0; i < v->size(); ++i) {
        msg = new Message((*v)[i]->getSession()->getID(),
                          Protocol::MSG_START_GAME_OK);
        (*v)[i]->getSession()->sendMessage(msg);
    }
}

void Engine::sendGameList(Session* session) {
    Message* msg = new Message(session->getID(), Protocol::MSG_LIST_GAMES);
    std::string gameInfo[5];
    Lock lock(games.getMutex());
    std::list<Game*>* l = games.getList();
    Game* game;
    for (std::list<Game*>::iterator it = l->begin(); it != l->end(); it++) {
        game = (*it);
        if (game->isActive()) continue; //Cannot join active games, do not show
        Campaign& campaign = game->getCampaign();
        gameInfo[0] = Util::itos(game->getID());
        gameInfo[1] = game->getName();
        gameInfo[2] = campaign.getName();
        gameInfo[3] = Util::itos(campaign.getDifficulty());
        gameInfo[4] = Util::itos(game->getPlayers()->size());
        msg->addRecord(gameInfo, 5);
    }
    session->sendMessage(msg);
}

void Engine::disconnect(Session* session) {
    Lock players_lock(players.getMutex());
    Player* player = getPlayer(session->getID());
    if (player == NULL) {
//        std::string txt("Game start denied");
  //      session->sendMessage(new Message(session->getID(),
    //                                     Protocol::MSG_START_GAME_FAIL,
      //                                   txt));
        return;
    }
    Game* game = getGame(player, false);
    //finish game
    //remove game
    //remove session
}

Session* Engine::getSession(std::string sessionID) {
    Session* session = NULL;
    Lock lock(app->getSessions().getMutex());
    std::list<Session*>* l = app->getSessions().getList();
    for (std::list<Session*>::iterator it = l->begin(); it != l->end(); it++) {
        if((*it)->getID() == sessionID) {
            session = *it;
            break;
        }
    }
    return session;
}

Game* Engine::getGame(int gameID) {
    std::list<Game*>* l = games.getList();
    for (std::list<Game*>::iterator it = l->begin(); it != l->end(); it++) {
        if((*it)->getID() == gameID) return *it;
    }
    return NULL;
}

Game* Engine::getGame(Player* player, bool is_owner) {
    std::list<Game*>* l = games.getList();
    if (is_owner) {
        for (std::list<Game*>::iterator it = l->begin(); it != l->end(); it++) {
            if((*it)->getOwner()->getID() == player->getID()) return *it;
        }
    } else {
        std::vector<Player*>* players;
        for (std::list<Game*>::iterator it = l->begin(); it != l->end(); it++) {
            players = (*it)->getPlayers();
            for (int i = 0; i < players->size(); ++i) {
                if ((*players)[i]->getID() == player->getID()) {
                    return (*it);
                } 
            }
        }
    }
    return NULL;
}

Player* Engine::getPlayer(std::string sessionID) {
    std::list<Player*>* l = players.getList();
    for (std::list<Player*>::iterator it = l->begin(); it != l->end(); it++) {
        if((*it)->getSession()->getID() == sessionID) return *it;
    }
    return NULL;
}

Campaign* Engine::getCampaign(int campaignID) {    
    Lock lock(app->getCampaigns().getMutex());
    std::vector<Campaign*>* v = app->getCampaigns().getVector();
    for (int i = 0; i < v->size(); ++i) {
        if ((*v)[i]->getID() == campaignID) return (*v)[i]; 
    }
    return NULL;
}

//{+}

